package daobeauty.interceptor.invoke.parser;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import daobeauty.interceptor.invoke.parser.operator.Operator;
import daobeauty.util.LinkedListStack;
import daobeauty.util.Stack;

public class ParserState {
	private static final Log LOG = LogFactory.getLog(ParserState.class);

	private final Stack<String> tokenStack = new LinkedListStack<String>();
	private final Stack<String> propertyStack = new LinkedListStack<String>();
	private final Stack<Class> argumentStack = new LinkedListStack<Class>();
	private final Stack<Operator> operatorStack = new LinkedListStack<Operator>();
	
	private final Stack<Stack<ParserAction>> actionStackStack = new LinkedListStack<Stack<ParserAction>>();
	private Stack<ParserAction> actualActionStack;
	
	public Stack<String> getTokenStack(){
		return tokenStack;
	}
	
	public Stack<String> getPropertyStack(){
		return propertyStack;
	}
	
	public Stack<Class> getArgumentStack(){
		return argumentStack;
	}
	
	public Stack<Operator> getOperatorStack(){
		return operatorStack;
	}
	 
	
	public void begin(){
		if (actualActionStack!=null){
			actionStackStack.push(actualActionStack);
		}
		actualActionStack = new LinkedListStack<ParserAction>();
	}
	
	public void rollback(){
		if (actualActionStack!=null){
			while (!actualActionStack.isEmpty()){
				actualActionStack.pop().rollback();
			}
			actualActionStack = null;
		}
		if (!actionStackStack.isEmpty()){
			actualActionStack = actionStackStack.pop();
		}
	}
	
	public Class popArgument(String expression) {
		if (LOG.isDebugEnabled()) {
			LOG.debug("argument(" + expression + ")");
			LOG.debug("  stack: " + argumentStack.toString());
		}
		Class argument = null;
		if (!argumentStack.isEmpty()) {
			argument = argumentStack.pop();
			if (actualActionStack != null){
				actualActionStack.push(new StackPopAction<Class>(argumentStack, argument));
			}
		}
		if (LOG.isDebugEnabled()) {
			LOG.debug("  return: \"" + String.valueOf(argument) + "\"");
		}

		return argument;
	}
	
	public void pushArgument(Class argument) {
		argumentStack.push(argument);
		if (actualActionStack != null){
			actualActionStack.push(new StackPushAction<Class>(argumentStack));
		}
	}
	
	public String popProperty() {
		if (LOG.isDebugEnabled()) {
			LOG.debug("popProperty()");
			LOG.debug("  before: " + propertyStack.toString());
		}
		final String property = propertyStack.pop();
		if (actualActionStack != null){
			actualActionStack.push(new StackPopAction<String>(propertyStack, property));
		}
		if (LOG.isDebugEnabled()) {
			LOG.debug("  after: " + propertyStack.toString());
			LOG.debug("  return: " + property);
		}
		return property;
	}

	public void pushProperty(String property) {
		if (LOG.isDebugEnabled()) {
			LOG.debug("pushProperty(" + property + ")");
			LOG.debug("  before: " + propertyStack.toString());
		}
		// hql erwartet d1.name statt d1.Name
		propertyStack.push(property);
		if (actualActionStack != null){
			actualActionStack.push(new StackPushAction<String>(propertyStack));
		}
		if (LOG.isDebugEnabled()) {
			LOG.debug("  after: " + propertyStack.toString());
		}
	}

	public Operator popOperator() {
		if (LOG.isDebugEnabled()) {
			LOG.debug("popOperator()");
			LOG.debug("  before: " + operatorStack.toString());
		}
		final Operator operator = operatorStack.pop();
		if (actualActionStack != null){
			actualActionStack.push(new StackPopAction<Operator>(operatorStack, operator));
		}
		if (LOG.isDebugEnabled()) {
			LOG.debug("  after: " + operatorStack.toString());
			LOG.debug("  return: " + operator);
		}
		return operator;
	}

	public void pushOperator(Operator operator) {
		if (LOG.isDebugEnabled()) {
			LOG.debug("pushOperator(" + operator + ")");
			LOG.debug("  before: " + operatorStack.toString());
		}
		operatorStack.push(operator);
		if (actualActionStack != null){
			actualActionStack.push(new StackPushAction<Operator>(operatorStack));
		}
		if (LOG.isDebugEnabled()) {
			LOG.debug("  after: " + operatorStack.toString());
		}
	}

	public String popToken() {
		if (LOG.isDebugEnabled()) {
			LOG.debug("popToken()");
			LOG.debug("  before: " + tokenStack.toString());
		}
		String token;
		if (tokenStack.isEmpty()) {
			token = null;
		}
		else{
			token = tokenStack.pop();
			if (actualActionStack != null){
				actualActionStack.push(new StackPopAction<String>(tokenStack, token));
			}
		}
		if (LOG.isDebugEnabled()) {
			LOG.debug("  after: " + tokenStack.toString());
			LOG.debug("  return: " + token);
		}

		return token;
	}

	public void pushToken(String token) {
		if (LOG.isDebugEnabled()) {
			LOG.debug("pushToken(" + token + ")");
			LOG.debug("  before: " + tokenStack.toString());
		}
		if (Character.isLowerCase(token.charAt(0))) {
			if (token.length() > 1) {
				token = Character.toUpperCase(token.charAt(0))
						+ token.substring(1);
			} else {
				token = Character.toUpperCase(token.charAt(0)) + "";
			}
		}
		tokenStack.push(token);
		if (actualActionStack != null){
			actualActionStack.push(new StackPushAction<String>(propertyStack));
		}
		if (LOG.isDebugEnabled()) {
			LOG.debug("  after: " + tokenStack.toString());
		}
	}
	
	
	
	private interface ParserAction{
		public void rollback();
	}
	
	private class StackPushAction<O> implements ParserAction{
		private Stack<O> stack;
		public StackPushAction(Stack<O> stack){
			this.stack = stack;
		}
		
		public void rollback(){
			stack.pop();
		}
	}
	
	private class StackPopAction<O> implements ParserAction{
		private Stack<O> stack;
		private O o;
		public StackPopAction(Stack<O> stack, O o){
			this.stack = stack;
			this.o = o;
		}
		
		public void rollback(){
			stack.push(o);
		}
	}
}
